package com.zhou.org.controller;

import cn.hutool.core.codec.Base64;
import com.google.code.kaptcha.Producer;
import com.zhou.framework.constant.CacheKeyEnum;
import com.zhou.framework.aop.anotation.LogOption;
import com.zhou.framework.aop.enums.LogLevel;
import com.zhou.framework.config.PasswordPolicyConfig;
import com.zhou.framework.exception.*;
import com.zhou.framework.jwt.annotation.PassToken;
import com.zhou.framework.jwt.model.LoginUser;
import com.zhou.framework.model.RS;
import com.zhou.framework.redis.util.RedisUtil;
import com.zhou.framework.util.SessionUtil;
import com.zhou.util.StringTool;
import com.zhou.org.common.EnumConstant;
import com.zhou.org.common.MenuUtil;
import com.zhou.org.dao.SysMenu;
import com.zhou.org.dto.LoginRequest;
import com.zhou.org.dto.UserInfoDto;
import com.zhou.org.model.LoginResult;
import com.zhou.org.model.RouterVo;
import com.zhou.org.service.ISysMenuService;
import com.zhou.org.service.LoginService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author lang.zhou
 * @date 2022/4/29 17:23
 */
@Api(tags = "登录接口")
@RestController
@Slf4j
public class LoginController {
    @Autowired
    private LoginService loginService;
    @Resource(name = "captchaProducer")
    private Producer captchaProducer;

    @Resource(name = "captchaProducerMath")
    private Producer captchaProducerMath;

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private ISysMenuService sysMenuService;

    @ApiOperation("登录")
    @LogOption(value = LogLevel.LOGIN,response = true)
    @PostMapping("/login")
    @PassToken
    public Object login(@RequestBody @Validated LoginRequest r){
        try{
            LoginResult result = loginService.login(r);
            return RS.data(result);
        }catch (VerifyException | LoginException e){

            return this.loginFailed(r.getUsername(),e);
        }catch (CaptchaException e){
            return RS.failed("验证码错误",500);
        }catch (UserLockException e){
            return RS.failed("用户被锁定",500);
        }catch (AuthException e){
            return RS.failed(e.getMessage(),403);
        } catch (Exception e){
            log.error("登录异常：",e);
            return RS.failed("登录异常",500);
        }
    }

    private Object loginFailed(String userName, Exception e){
        String msg = "";
        PasswordPolicyConfig instance = PasswordPolicyConfig.getInstance();
        if(instance.isEnable()){
            String key = CacheKeyEnum.PASSWORD_ERROR_COUNT + "||" + userName;
            Long number = redisUtil.incr(key, instance.getPasswordMaxErrorCount(), 60 * instance.getPasswordMaxErrorTime());
            long n = instance.getPasswordMaxErrorCount();
            if (number == null || number.intValue() > n){
                return RS.failed("用户被锁定，请" + instance.getPasswordMaxErrorTime() + "分钟后重试");
            }
            msg = "，还能尝试" + (n - number + 1) + "次";
        }
        return RS.failed(e.getMessage() + msg,500);
    }

    /**
     * 获取用户信息
     *
     * @return 用户信息
     */
    @ApiOperation("获取用户信息")
    @LogOption(value = LogLevel.QUERY,response = true)
    @GetMapping("getInfo")
    public Object getInfo() {
        LoginUser user = SessionUtil.getCurrentUserSafe();
        RS r = RS.success();
        if(user != null){
            r.put("userInfo", new UserInfoDto(user.getUser()));
        }
        return r;
    }

    @PassToken
    @ApiOperation("生成验证码")
    @LogOption(value = LogLevel.QUERY,response = true,excludeResponse = "img")
    @GetMapping("/captchaImage")
    public RS getCode() throws IOException {
        RS rs = RS.success();
        Boolean captchaEnabled = PasswordPolicyConfig.getInstance().isCaptchaEnable();
        rs.put("captchaEnabled",captchaEnabled);
        if(!captchaEnabled){
            return rs;
        }
        String captchaType = PasswordPolicyConfig.getInstance().getCaptchaType();
        // 保存验证码信息
        String uuid = StringTool.uuid();
        String verifyKey = CacheKeyEnum.CAPTCHA_CODE_KEY + uuid;

        String capStr, code;
        BufferedImage image;

        // 生成验证码
        if ("math".equals(captchaType)) {
            String capText = captchaProducerMath.createText();
            capStr = capText.substring(0, capText.lastIndexOf("@"));
            code = capText.substring(capText.lastIndexOf("@") + 1);
            image = captchaProducerMath.createImage(capStr);
        } else if ("char".equals(captchaType)) {
            capStr = code = captchaProducer.createText();
            image = captchaProducer.createImage(capStr);
        }else{
            return RS.failed("不支持的验证码类型");
        }

        redisUtil.set(verifyKey, code, 2*60L);
        // 转换流信息写出
        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
        try {
            ImageIO.write(image, "jpg", os);
        } catch (IOException e) {
            return RS.failed(e.getMessage());
        }
        return rs.put("uuid", uuid).put("img", Base64.encode(os.toByteArray()));
    }

    @ApiOperation("查询用户有权限的路由菜单")
    @LogOption(LogLevel.QUERY)
    @GetMapping("getMenu")
    public Object getAuthMenu(){
        Integer orgId = SessionUtil.getCurrentOrgId();
        List<SysMenu> sysMenus = sysMenuService.selectMenuTreeByUserId(orgId);
        List<String> auth = sysMenus.stream()
                .filter(o-> EnumConstant.TYPE_BUTTON.equals(o.getMenuType()))
                .map(SysMenu::getPerVal)
                .collect(Collectors.toList());
        List<SysMenu> collect = sysMenus.stream().filter(o -> !Objects.equals(EnumConstant.TYPE_BUTTON, o.getMenuType()) && !Objects.equals(EnumConstant.TYPE_INTF, o.getMenuType()))
                .collect(Collectors.toList());
        List<SysMenu> list = MenuUtil.getChildPerms(collect, 0);
        List<RouterVo> routerVos = MenuUtil.buildMenus(list);
        return RS.success().set("router",routerVos).set("authBtn",auth);
    }

    @ApiOperation("退出登录")
    @LogOption(LogLevel.LOGOUT)
    @PostMapping("/logout")
    public RS logout(){
        try{
            LoginUser user = SessionUtil.getCurrentUser();
            SessionUtil.logout();
            return RS.data(user.getUser());
        }catch (Exception e){
            log.error("登出失败：",e);
        }
        return RS.success();
    }
}
